package cn.iocoder.yudao.module.call.service.notify;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.call.dal.dataobject.agent.AgentDO;
import cn.iocoder.yudao.module.call.dal.dataobject.taskcalleelistnumber.CallTaskCalleeListNumberDO;
import cn.iocoder.yudao.module.call.dal.mysql.agent.AgentMapper;
import cn.iocoder.yudao.module.call.enums.*;
import cn.iocoder.yudao.module.call.framework.client.CallClient;
import cn.iocoder.yudao.module.call.framework.client.CallClientFactory;
import cn.iocoder.yudao.module.call.api.client.dto.CallNotifyDTO;
import cn.iocoder.yudao.module.call.api.client.dto.CallReqDTO;
import cn.iocoder.yudao.module.call.framework.client.impl.box.BoxClient;
import cn.iocoder.yudao.module.call.framework.client.impl.cm.CmClient;
import cn.iocoder.yudao.module.call.framework.client.impl.okcc.OkccClient;
import cn.iocoder.yudao.module.call.framework.client.impl.sim.SimClient;
import cn.iocoder.yudao.module.call.dal.dataobject.record.CallRecordDO;
import cn.iocoder.yudao.module.call.dal.dataobject.task.CallTaskDO;
import cn.iocoder.yudao.module.call.dal.mysql.record.CallRecordMapper;
import cn.iocoder.yudao.module.call.dal.redis.agent.AgentRedisKeyConstant;
import cn.iocoder.yudao.module.call.dal.redis.call.CallRedisKeyConstant;
import cn.iocoder.yudao.module.call.mq.message.intention.IntentionMessage;
import cn.iocoder.yudao.module.call.mq.producer.bill.CallBillProducer;
import cn.iocoder.yudao.module.call.mq.producer.follow.CallFollowProducer;
import cn.iocoder.yudao.module.call.mq.producer.intention.IntentionProducer;
import cn.iocoder.yudao.module.call.service.ivrintentionlabelstatistics.IvrIntentionLabelStatisticsService;
import cn.iocoder.yudao.module.call.service.notify.dto.CrmClueTransConfigDTO;
import cn.iocoder.yudao.module.call.service.task.CallTaskService;
import cn.iocoder.yudao.module.call.service.taskcalleelistnumber.CallTaskCalleeListNumberService;
import cn.iocoder.yudao.module.crm.api.clue.CrmClueApi;
import cn.iocoder.yudao.module.crm.api.location.NumberLocationApi;
import cn.iocoder.yudao.module.crm.api.location.dto.NumberLocationDTO;
import cn.iocoder.yudao.module.system.api.tenant.TenantApi;
import cn.iocoder.yudao.module.system.enums.product.ProductEnum;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Service
@Validated
@Slf4j
public class CallNotifyServiceImpl implements CallNotifyService {


    @Resource
    private CallClientFactory callClientFactory;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private CallRecordMapper callRecordMapper;

    @Resource
    private CallBillProducer callBillProducer;

    @Resource
    private CallFollowProducer callFollowProducer;

    @Resource
    private IntentionProducer intentionProducer;

    @Resource
    private AgentMapper agentMapper;

    @Resource
    private TenantApi tenantApi;

    @Resource
    private CallTaskService callTaskService;

    @Resource
    private CallTaskCalleeListNumberService callTaskCalleeListNumberService;

    @Resource
    @Lazy
    private CrmClueApi crmClueApi;

    @Resource
    private NumberLocationApi numberLocationApi;

    @Override
    public boolean handleRecord(String code, JSONObject jsonObject) {
        CallClient callClient = callClientFactory.getCallClient(code);
        Assert.notNull(callClient, "未找到相关SDK");
        log.debug("话单推送 ============== \r\n{}", jsonObject);
        CallNotifyDTO callNotifyDTO = callClient.handleNotify(jsonObject);
        log.debug("解析对象 ============== \r\n{}", callNotifyDTO);
        if (Objects.isNull(callNotifyDTO)) {
            return false;
        }
        return handleNotify(callClient, callNotifyDTO);
    }

    @Override
    public boolean handleRecord(Long id, JSONObject jsonObject) {
        CallClient callClient = callClientFactory.getCallClient(id);
        Assert.notNull(callClient, "未找到相关SDK");

        CallNotifyDTO callNotifyDTO = callClient.handleNotify(jsonObject);

        return handleNotify(callClient, callNotifyDTO);
    }

    private Boolean handleNotify(CallClient callClient, CallNotifyDTO callNotifyDTO) {
        if (Objects.equals("bill", callNotifyDTO.getType())) {
            return handleBillNotify(callClient, callNotifyDTO);
        } else if (Objects.equals("task", callNotifyDTO.getType())) {
            return handleTaskNotify(callNotifyDTO);
        }
        return Boolean.FALSE;
    }

    public boolean handleTaskNotify(CallNotifyDTO callNotifyDTO) {
        // 更新任务的执行状态；
        CallTaskDO callTaskDO = new CallTaskDO();
        callTaskDO.setTaskId(callNotifyDTO.getBatchTaskId());
        callTaskDO.setNumberCount(callNotifyDTO.getCalleeAmount());
        callTaskDO.setCallCount(callNotifyDTO.getCalleeAmount() - callNotifyDTO.getRemainAmount());
        callTaskDO.setProgress(
                NumberUtil.div(callTaskDO.getCallCount(), callTaskDO.getNumberCount()).multiply(new BigDecimal(100)).intValue()
        );
        Integer status = convertTaskStatus(callNotifyDTO.getStatus());
        callTaskDO.setStatus(status);
        callTaskService.simpleUpdateTaskByTaskId(callTaskDO);
        return true;
    }

    private Integer convertTaskStatus(Integer status) {
        if (status == 0) {
            return CallTaskStatusEnum.WAITING.getStatus();
        }
        if (status == 1) {
            return CallTaskStatusEnum.RUNNING.getStatus();
        }
        if (status == 2) {
            return CallTaskStatusEnum.PAUSE.getStatus();
        }
        if (status == 3) {
            return CallTaskStatusEnum.STOP.getStatus();
        }
        return CallTaskStatusEnum.ERROR.getStatus();
    }

    @Override
    public boolean handleBillNotify(CallClient callClient, CallNotifyDTO callNotifyDTO) {

        String reqNo = null;
        String cacheReqNo = ""; //从缓存中取通话信息的key
        boolean isSimCardCall;
        if (callClient instanceof BoxClient) {
            isSimCardCall = true;
            cacheReqNo = reqNo = stringRedisTemplate.opsForValue().get(CallRedisKeyConstant.CALL_REQ_NO_MAP_CALL_ID + callNotifyDTO.getUserData());
        } else if (callClient instanceof SimClient) {
            isSimCardCall = true;
            cacheReqNo = reqNo = callNotifyDTO.getReqNo();
        } else if (callClient instanceof OkccClient) {
            isSimCardCall = false;
            reqNo = callNotifyDTO.getReqNo();
            cacheReqNo = callNotifyDTO.getUserData();
        } else {
            isSimCardCall = false;
            cacheReqNo = reqNo = callNotifyDTO.getReqNo();
        }
        //防止重复插入
        CallRecordDO callRecordExists = callRecordMapper.selectOne(new LambdaQueryWrapperX<CallRecordDO>().eq(CallRecordDO::getReqNo, reqNo).last("limit 1"));
        if (!Objects.isNull(callRecordExists)) {
            return true;
        }
        CallReqDTO callReqDTO = null;
        CallTaskDO callTaskDO;
        if (callNotifyDTO.isBatchTask()) {
            log.debug("处理话单 ====== isTask: {}", callNotifyDTO);
            if (Objects.equals(callNotifyDTO.getResultType(), CallResultTypeEnum.SUCCESS.getValue())) {
                cacheReqNo = stringRedisTemplate.opsForValue().get(CallRedisKeyConstant.CALL_REQ_NO_MAP_CALL_ID + cacheReqNo);
                if (StrUtil.isNotEmpty(cacheReqNo)) {
                    callReqDTO = getCallReqDTOFromCache(cacheReqNo);
                }
            }
            if (callReqDTO == null) {
                //先查询坐席绑定关系
                AgentDO agentDO = agentMapper.selectOne(AgentDO::getAgentNumber, callNotifyDTO.getStaffNo());
                callTaskDO = callTaskService.getTaskByTaskIdStr(callNotifyDTO.getBatchTaskId());
                if (Objects.nonNull(agentDO)) {
                    callReqDTO = new CallReqDTO();
                    callReqDTO.setCallType(CallTypeEnum.BATCH_TASK_CALL.getValue());
                    callReqDTO.setCallee(callNotifyDTO.getCallee());
                    callReqDTO.setTenantId(agentDO.getTenantId());
                    callReqDTO.setChannelId(callClient.getId());
                    callReqDTO.setOwnerUserId(agentDO.getOwnerUserId());
                } else {
                    // 如果查不到就根据任务来
                    if (Objects.nonNull(callTaskDO)) {
                        callReqDTO = new CallReqDTO();
                        callReqDTO.setCallType(CallTypeEnum.BATCH_TASK_CALL.getValue());
                        callReqDTO.setCallee(callNotifyDTO.getCallee());
                        callReqDTO.setTenantId(callTaskDO.getTenantId());
                        callReqDTO.setChannelId(callClient.getId());
                        callReqDTO.setOwnerUserId(callTaskDO.getOwnerUserId());
                    }
                }
                if (callClient instanceof CmClient && Objects.nonNull(callTaskDO)) {
                    //转IVR类型的和语音播报后转坐席的 只要接通就是成功
                    if (callNotifyDTO.getResultCode().equals("leak")
                            && (Objects.equals(callTaskDO.getType(), CmTaskTypeEnum.TO_IVR.getType()) || Objects.equals(callTaskDO.getType(), CmTaskTypeEnum.AUDIO_TO_AGENT.getType()))
                    ) {
                        callNotifyDTO.setResultCode("success");
                        callNotifyDTO.setResultType(CallResultTypeEnum.SUCCESS.getValue());
                        callNotifyDTO.setResultMsg(CallResultTypeEnum.SUCCESS.getMsg());
                    }

                    if (Objects.equals(callTaskDO.getType(), CmTaskTypeEnum.TO_IVR.getType())) {
                        //类型为转ivr的标识
                        callReqDTO.setCallType(CallTypeEnum.BATCH_IVR_CALL.getValue());
                    }
                }

            } else {
                callTaskDO = null;
            }

        } else {
            callTaskDO = null;

            callReqDTO = getCallReqDTOFromCache(cacheReqNo);
            if (callReqDTO == null) {
                //对于未查询到缓存的直接查询坐席信息
                AgentDO agentDO = agentMapper.selectOne(AgentDO::getAgentNumber, callNotifyDTO.getStaffNo());
                if (Objects.nonNull(agentDO)) {
                    callReqDTO = new CallReqDTO();
                    callReqDTO.setCallType(CallTypeEnum.SIMPLE_CALL.getValue());
                    callReqDTO.setTenantId(agentDO.getTenantId());
                    callReqDTO.setChannelId(callClient.getId());
                    callReqDTO.setOwnerUserId(agentDO.getOwnerUserId());
                }
            }
        }

        if (Objects.isNull(callReqDTO)) {
            return false;
        }
        callReqDTO.setCallee(callNotifyDTO.getCallee()); //有时缓存中会没有callee,直接使用远端获取的
        CallRecordDO callRecordDO = BeanUtils.toBean(callReqDTO, CallRecordDO.class);
        BigDecimal callCostPrice = tenantApi.getCallCostPrice(callRecordDO.getTenantId());
        BigDecimal callSalePrice = tenantApi.getCallSalePrice(callRecordDO.getTenantId());
        if (StrUtil.isEmpty(reqNo)) {
            reqNo = callNotifyDTO.getReqNo();
        }
        callRecordDO.setCallSecond(callNotifyDTO.getCallSecond())
                .setCallMinute(callNotifyDTO.getCallMinute())
                .setCreateTime(callNotifyDTO.getStartTime())
                .setStartTime(callNotifyDTO.getAnswerTime())
                .setEndTime(callNotifyDTO.getEndTime())
                .setResultType(callNotifyDTO.getResultType())
                .setResultMsg(callNotifyDTO.getResultMsg())
                .setTempRecordUrl(callNotifyDTO.getTempRecordUrl())
                .setRecordUrl(callNotifyDTO.getRecordUrl())
                .setDownloadStatus(false)
                .setReqNo(reqNo)
                .setPhoneName(callClient.getName())
                .setTaskId(callNotifyDTO.getBatchTaskId())
                .setUserData(callNotifyDTO.getUserData())
                .setResultCode(callNotifyDTO.getResultCode())
                .setCallCostPrice(callCostPrice.multiply(new BigDecimal(callNotifyDTO.getCallMinute())))
                .setCallSalePrice(callSalePrice.multiply(new BigDecimal(callNotifyDTO.getCallMinute())))
                .setAgentNumber(callNotifyDTO.getStaffNo());
        NumberLocationDTO locationDTO = numberLocationApi.getLocation(callRecordDO.getCallee());
        if (Objects.nonNull(locationDTO)) {
            //号码归属地
            callRecordDO.setCity(locationDTO.getCity());
            callRecordDO.setProvinces(locationDTO.getProvinces());
            callRecordDO.setFacilitator(locationDTO.getFacilitator());
        }

        if (StrUtil.isNotEmpty(callNotifyDTO.getRecordUrl())) {
            callRecordDO.setDownloadStatus(true);
        }
        String remark = stringRedisTemplate.opsForValue().get("call_record_remark:" + reqNo);
        if (StrUtil.isNotEmpty(remark)) {
            callRecordDO.setRemark(remark);
            stringRedisTemplate.delete("call_record_remark:" + reqNo);
        }
        stringRedisTemplate.delete(AgentRedisKeyConstant.AGENT_STATUS + callRecordDO.getOwnerUserId());

        TenantUtils.execute(callRecordDO.getTenantId(), () -> {
            if (!isSimCardCall) {
                if (callRecordDO.getCallMinute() > 0) {
                    if (PhoneModeEnum.isSIP(callClient.getMode())) {
                        if (callNotifyDTO.isBatchTask()) {
                            Long balance = tenantApi.updateTenantProductBalance(ProductEnum.BATCH_CALL.getCode(), callRecordDO.getTenantId(), false, callRecordDO.getCallMinute());
                            if (balance <= 0) {
                                callTaskService.stopAllTask();
                            }
                        } else {
                            long less = tenantApi.updateTenantProductBalance(ProductEnum.CORP_SIMPLE_CALL.getCode(), callRecordDO.getTenantId(), false, callRecordDO.getCallMinute());
                            if (less < 0) {
                                tenantApi.updateTenantProductBalance(ProductEnum.SIMPLE_CALL.getCode(), callRecordDO.getOwnerUserId(), false, callRecordDO.getCallMinute());
                            }
                        }
                    } else if (PhoneModeEnum.isRECALL(callClient.getMode())) {
                        tenantApi.updateTenantProductBalance(ProductEnum.DOUBLE_CALL.getCode(), callRecordDO.getOwnerUserId(), false, callRecordDO.getCallMinute());
                    }
                    callBillProducer.sendCallBillMessage(
                            callRecordDO.getOwnerUserId(),
                            callRecordDO.getTenantId(),
                            callRecordDO.getCallMinute(),
                            callRecordDO.getCallType(),
                            callRecordDO.getChannelId(),
                            callCostPrice.doubleValue(),
                            callSalePrice.doubleValue()
                    );
                }
            }
            String config = stringRedisTemplate.opsForValue().get("crm:clue_trans_config:" + callRecordDO.getTenantId());
            if (StrUtil.isNotEmpty(config)) {
                CrmClueTransConfigDTO configDTO = JSONUtil.toBean(config, CrmClueTransConfigDTO.class);
                if (!StrUtil.equals("clue", callRecordDO.getSource()) && StrUtil.isEmpty(callRecordDO.getSourceId())) {
                    if (callRecordDO.getCallSecond() >= configDTO.getTransClueSeconds()) {
                        Long clueId = crmClueApi.createClueByCallRecord(callRecordDO.getTenantId(),
                                configDTO.getModuleId(), callRecordDO.getOwnerUserId(), callRecordDO.getCallee(), callRecordDO.getReqNo());
                        callRecordDO.setSourceId(clueId + "");
                        callRecordDO.setSource("clue");
                        callRecordDO.setIsClue(1);
                        callRecordDO.setClueId(clueId);
                    }
                }
            }
            //呼叫名单信息 没有任何来源的群呼 查一下呼叫名单信息
            if (callNotifyDTO.isBatchTask() && StrUtil.isEmpty(callRecordDO.getSourceId())) {
                CallTaskCalleeListNumberDO callTaskCalleeListNumber = callTaskCalleeListNumberService.getByNumber(callRecordDO.getCallee());
                if (ObjUtil.isNotEmpty(callTaskCalleeListNumber)) {
                    callRecordDO.setSource(CallSourceEnum.LIST.getId());
                    callRecordDO.setSourceId(callTaskCalleeListNumber.getId().toString());
                }
            }
            callRecordMapper.insert(callRecordDO);
            callFollowProducer.sendCallFollowMessage(
                    callRecordDO.getSource(),
                    callRecordDO.getOwnerUserId(),
                    callRecordDO.getSourceId(),
                    callRecordDO.getCallee(),
                    callRecordDO.getId(),
                    callRecordDO.getResultType().equals(CallResultTypeEnum.SUCCESS.getValue()),
                    callRecordDO.getRecordUrl(),
                    callClient.getName()
            );
            //呼叫任务后续处理
            if (callTaskDO != null && callTaskDO.getIvrFlowId() != null) {
                intentionProducer.sendIntentionMessage(callTaskDO.getIvrFlowId(), callRecordDO.getReqNo(), callRecordDO.getCallee(), callTaskDO.getId());
            }

        });
        return true;
    }

    private CallReqDTO getCallReqDTOFromCache(String reqNo) {
        String cacheCallStr = stringRedisTemplate.opsForValue().get(CallRedisKeyConstant.CALL_REQ_NO + reqNo);
        if (ObjUtil.isNull(cacheCallStr)) {
            return null;
        }
        stringRedisTemplate.expire(CallRedisKeyConstant.CALL_REQ_NO + reqNo, 30, TimeUnit.MINUTES);
        return JSONUtil.toBean(cacheCallStr, CallReqDTO.class);
    }

    @Override
    public boolean handleBind(String code, JSONObject jsonObject) {
        return false;
    }

    @Override
    public boolean handleUnBind(String code, JSONObject jsonObject) {
        return false;
    }

    @Override
    public boolean handleCallStart(String code, JSONObject jsonObject) {
        return false;
    }

    @Override
    public boolean handleCallEnd(String code, JSONObject jsonObject) {
        return false;
    }
}
